use apikit::{ApiErrorItem, StatusCode};
use axum::{Router, body::Body, extract::Request, response::Response};
use axum_typed_multipart::async_trait;
use carpet_base::server::error_to_response;
use inventory::Collect;
use tokio::signal;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};

use crate::handler::MethodHandlerStraw;

#[async_trait]
pub trait Server: Sized + 'static {
    type State: Send + Sync + Clone;
    type HandlerStraw: Collect + MethodHandlerStraw<S = Self::State>;

    fn addr(&self) -> Result<SocketAddr, anyhow::Error> {
        Ok(std::net::SocketAddr::V4(SocketAddrV4::new(
            Ipv4Addr::from([0, 0, 0, 0]),
            9000,
        )))
    }

    fn state(&self) -> Self::State;

    fn routes(&self) -> Router<Self::State> {
        let mut routers: Router<Self::State> = Router::new();
        for m in inventory::iter::<Self::HandlerStraw> {
            let router_handler_fn = m.path_hander_fn();
            let item = router_handler_fn();
            routers = routers.route(&item.path, item.handler.fallback(Self::fallback405));
        }
        routers
    }

    fn startup_message(&self, _addr: &SocketAddr) {}

    async fn fallback404(_req: Request<Body>) -> Response {
        let e = ApiErrorItem::from(StatusCode::NOT_FOUND);
        error_to_response(e)
    }

    async fn fallback405(_req: Request<Body>) -> Response {
        let e = ApiErrorItem::from(StatusCode::METHOD_NOT_ALLOWED);
        error_to_response(e)
    }

    async fn shutdown_signal() {
        let ctrl_c = async {
            signal::ctrl_c()
                .await
                .expect("failed to install Ctrl+C handler");
        };

        #[cfg(unix)]
        let terminate = async {
            signal::unix::signal(signal::unix::SignalKind::terminate())
                .expect("failed to install signal handler")
                .recv()
                .await;
        };

        #[cfg(not(unix))]
        let terminate = std::future::pending::<()>();

        tokio::select! {
            _ = ctrl_c => {},
            _ = terminate => {},
        }
    }

    async fn run(self) -> Result<(), anyhow::Error> {
        let addr = self.addr()?;
        let listener = tokio::net::TcpListener::bind(&addr).await?;
        self.startup_message(&addr);
        axum::serve(
            listener,
            self.routes()
                .with_state(self.state())
                .fallback(Self::fallback404)
                .into_make_service_with_connect_info::<SocketAddr>(),
        )
        .with_graceful_shutdown(Self::shutdown_signal())
        .await?;
        Ok(())
    }
}
